#!/usr/bin/env python

import sys
import getopt
import simplejson
import urllib
import re

class Generator:
    property_simple_template = \
        'this.prop("%(name)s", "%(cap_name)s", null, %(stub)s);'
        
    property_complex_template = \
        'this.prop("%(name)s", "%(cap_name)s", function () {return %(type)s;}, %(stub)s);'
        
    property_doc = '  <li><strong>%s</strong> (%s): %s</li>'
    
    type_resource_template = 'j.ResourceType(ns.%s)'
    type_resource_doc = '<a href="#%(name)s">%(name)s</a>'
    type_stub_template = 'j.MakeStub(%s)'
    type_stub_doc = '<a href="#Stub">Stub</a> of a %s'
    type_link_template = 'j.MakeLink(%s)'
    type_link_doc = '<a href="#Link">Link</a> to a %s'
    type_list_template = 'j.MakeList(%s)'
    type_list_doc = '<a href="#List">List</a> of a %s'
    
    class_template = """
ns.%(name)s = j.extend(j.Resource, {
    constructor: function () {
        j.Resource.call(this);
        
%(properties)s
    }
});

j.CreateResourceFactory(ns.%(name)s, %(path)s, ns, %(id)s);
"""
    
    class_doc_start = """
<div class="resource">
<a name="%(name)s"></a>
<h3>%(name)s</h3>

<p class="info">
"""
    
    class_doc_properties = """
</p>

<ul>
"""
    
    class_doc_end = """
</ul>
</div>
"""
    
    page_template = """
(function () {
var j = JRDL;
j.namespace("%(namespace)s");
var ns = %(namespace)s = new j.Service("%(path)s");

%(classes)s

})();
"""
    
    page_doc_start = """
<html>
<head>
<title>Namespace: %(namespace)s</title>
<style type="text/css">
body {
    font-family:Arial, Helvetica, sans-serif;
}

#static-methods,
#instance-methods,
#property-methods,
#property-events,
#resource-classes,
#resource-types {
    margin-bottom:50px;
}

.method {
    margin:5px;
    padding:5px;
    background-color:#e0ebff;
    border:1px solid #bed4fb;
}

.method .description {
    padding-left:10px;
}

.method ul {
    margin-left:25px;
}

.method li {
    padding-bottom:5px;
}

.event {
    margin:5px;
    padding:5px;
    background-color:#ffdede;
    border:1px solid #ffb7b7;
}

.event .info {
    padding-left:10px;
}

.event ul {
    margin-left:25px;
}

.event li {
    padding-bottom:10px;
}

.resource {
    margin:5px;
    padding:5px;
    background-color:#e3ffde;
    border:1px solid #c8fbbe;
}

.resource .info {
    padding-left:10px;
}

.resource ul {
    margin-left:25px;
}

.resource li {
    padding-bottom:10px;
}
</style>
</head>
<body>
<h1>Namespace: %(namespace)s</h1>
<h2>Path: %(path)s</h2>

<div id="static-methods">
<p>
Each class in this namepsace will have the following static methods:
</p>

<div class="method">
<a name="method-static-list"></a>
<p class="signature">
void <strong>list</strong>(Function <strong>callback</strong>, Object <strong>scope</strong>)
</p>

<p class="description">
Retrieves a <a href="#List">List</a> instance containing all of the instances of this class from the server.
</p>

<ul class="properties">
  <li><strong>callback</strong>: The function to be called upon receipt of the server's response. 
  The callback is called regardless of success or failure and is passed the 
  following parameters:
  <ul>
    <li><strong>list</strong>: A <a href="#List">List</a> instance that contains all of the instances returned by 
    the server, or null if the server request fails.</li>
  </ul>
  </li>
  <li><strong>scope</strong>: The scope in which to execute the callback.</li>
</ul>
</div>

<div class="method">
<a name="method-static-load"></a>
<p class="signature">
void <strong>load</strong>(Mixed <strong>id</strong>, Function <strong>callback</strong>, Object <strong>scope</strong>)
</p>

<p class="description">
Retrieves an instance of this class from the server.
</p>

<ul class="properties">
  <li><strong>id</strong>: The unique id of the instance you want to retrieve.</li>
  <li><strong>callback</strong>: The function to be called upon receipt of the server's response. 
  The callback is called regardless of success or failure and is passed the 
  following parameters:
  <ul>
    <li><strong>obj</strong>: The instance of the class returned by the server, or null if the
    server request fails.</li>
  </ul>
  </li>
  <li><strong>scope</strong>: The scope in which to execute the callback.</li>
</ul>
</div>
</div>

<div id="instance-methods">
<p>
Each class instance will have the following methods:
</p>

<div class="method">
<a name="method-load"></a>
<p class="signature">
void <strong>load</strong>(Function <strong>callback</strong>, Object <strong>scope</strong>)
</p>

<p class="description">
Loads the rest of this instance from the server if only the link or stub are 
loaded.
</p>

<ul class="properties">
  <li><strong>callback</strong>: The function to be called upon receipt of the server's response. 
  The callback is called regardless of success or failure and is passed the 
  following parameters:
  <ul>
    <li><strong>obj</strong>: The instance of the class returned by the server, or null if the
    server request fails.</li>
  </ul>
  </li>
  <li><strong>scope</strong>: The scope in which to execute the callback.</li>
</ul>
</div>

<div class="method">
<a name="method-reload"></a>
<p class="signature">
void <strong>reload</strong>(Function <strong>callback</strong>, Object <strong>scope</strong>)
</p>

<p class="description">
Reloads this instance from the server.
</p>

<ul class="properties">
  <li><strong>callback</strong>: The function to be called upon receipt of the server's response. 
  The callback is called regardless of success or failure and is passed the 
  following parameters:
  <ul>
    <li><strong>obj</strong>: The instance of the class returned by the server, or null if the
    server request fails.</li>
  </ul>
  </li>
  <li><strong>scope</strong>: The scope in which to execute the callback.</li>
</ul>
</div>

<div class="method">
<a name="method-save"></a>
<p class="signature">
void <strong>save</strong>(Function <strong>callback</strong>, Object <strong>scope</strong>)
</p>

<p class="description">
Saves this instance to the server.
</p>

<ul class="properties">
  <li><strong>callback</strong>: The function to be called upon receipt of the server's response. 
  The callback is called regardless of success or failure and is passed the 
  following parameters:
  <ul>
    <li><strong>obj</strong>: The instance of the class returned by the server, or null if the
    server request fails.</li>
  </ul>
  </li>
  <li><strong>scope</strong>: The scope in which to execute the callback.</li>
</ul>
</div>

<div class="method">
<a name="method-remove"></a>
<p class="signature">
void <strong>remove</strong>(Function <strong>callback</strong>, Object <strong>scope</strong>)
</p>

<p class="description">
Removes this instance from the server.
</p>

<ul class="properties">
  <li><strong>callback</strong>: The function to be called upon receipt of the server's response. 
  The callback is called regardless of success or failure and is passed the 
  following parameters:
  <ul>
    <li><strong>obj</strong>: The instance of the class returned by the server, or null if the
    server request fails.</li>
  </ul>
  </li>
  <li><strong>scope</strong>: The scope in which to execute the callback.</li>
</ul>
</div>

<div class="method">
<a name="method-getLink"></a>
<p class="signature">
String <strong>getLink</strong>()
</p>

<p class="description">
Gets the unique url of this instance.
</p>
</div>

<div class="method">
<a name="method-isLinkLoaded"></a>
<p class="signature">
Boolean <strong>isLinkLoaded</strong>()
</p>

<p class="description">
Is this instance's unique url loaded?
</p>
</div>

<div class="method">
<a name="method-isStubLoaded"></a>
<p class="signature">
Boolean <strong>isStubLoaded</strong>()
</p>

<p class="description">
Is this instance's stub loaded?
</p>
</div>

<div class="method">
<a name="method-isLoaded"></a>
<p class="signature">
Boolean <strong>isLoaded</strong>()
</p>

<p class="description">
Is this instance completely loaded?
</p>
</div>

<div class="method">
<a name="method-isBusy"></a>
<p class="signature">
Boolean <strong>isBusy</strong>()
</p>

<p class="description">
Is this instance currently busy talking to the server?
</p>
</div>
</div>

<div id="property-methods">
<p>
For each property of a class, each class instance will have the following 
methods: 
</p>

<div class="method">
<a name="method-get"></a>
<p class="signature">
Mixed <strong>get[Name]</strong>()
</p>
</div>

<div class="method">
<a name="method-set"></a>
<p class="signature">
Void <strong>set[Name]</strong>(Mixed <strong>value</strong>)
</p>
</div>

<div class="method">
<a name="method-getType"></a>
<p class="signature">
Mixed <strong>get[Name]Type</strong>()
</p>
</div>
</div>

<div id="property-events">
<p>
For each property of a class, each class instance will have the following 
events: 
</p>

<div class="event">
<a name="event-updated"></a>
<p>
<strong>[name]_updated</strong> - fired when the property's <a href="#method-set">set</a> method is called.
</p>

<p class="info">
Event listeners will be called with the following parameters:
</p>

<ul>
  <li><strong>value</strong> - The new value of the property.</li>
</ul>
</div>
</div>

<div id="resource-classes">
<h2>Classes</h2>

"""
    
    page_doc_end = """
</div>

<div id="resource-types">
<h2>Types</h2>
<div class="resource">
<a name="String"></a>
<a name="Number"></a>
<a name="Boolean"></a>
<p>
<strong>String</strong>, <strong>Number</strong> and <strong>Boolean</strong> represent the basic 
JavaScript types.
</p>
</div>

<div class="resource">
<a name="Stub"></a>
<p>
<strong>Stub</strong>
</p>

<p class="info">
A Stub represents an instance of a class that only has a subset of properties loaded from the server.
</p>

<p class="info">
To access the non-stub properties of the instance you must call the instance's <a href="#method-load">load</a> method.
</p>
</div>

<div class="resource">
<a name="Link"></a>
<p>
<strong>Link</strong>
</p>

<p class="info">
A Link represents an instance of a class that only has a unique url loaded from the server.
</p>

<p class="info">
To access the properties of the instance you must call the instance's <a href="#method-load">load</a> method.
</p>
</div>

<div class="resource">
<a name="List"></a>
<p>
<strong>List</strong>
</p>

<p class="info">
A List represents a List class instance that contains a list of instances of a specified type.
</p>

<p class="info">
List class instances have all of the methods of other classes, but the <a href="#method-save">save<a> and 
<a href="#method-remove">remove</a> methods do nothing.
</p>
</div>

<p>
List class instances also implement the following methods:
</p>

<div class="method">
<a name="method-getItems"></a>
<p class="signature">
Object[] <strong>getItems</strong>()
</p>

<p class="description">
Gets an array containing all of the items the instance has.
</p>
</div>

<div class="method">
<a name="method-getItemCount"></a>
<p class="signature">
Number <strong>getItemCount</strong>()
</p>

<p class="description">
Gets the number of items the instance has.
</p>
</div>

<div class="method">
<a name="method-clear"></a>
<p class="signature">
Void <strong>clear</strong>()
</p>

<p class="description">
Removes all of the items the instance has.  This does not remove the items completely, just from this instance.
</p>
</div>

<div class="method">
<a name="method-addItem"></a>
<p class="signature">
Void <strong>addItem</strong>(Object <strong>item</strong> | Object[] <strong>items</strong>)
</p>

<p class="description">
Adds an item or items from the instance.
</p>
</div>

<div class="method">
<a name="method-removeItem"></a>
<p class="signature">
Void <strong>removeItem</strong>(Object <strong>item</strong> | Object[] <strong>items</strong>)
</p>

<p class="description">
Removes an item or items from the instance.  This does not remove the items completely, just from this instance.
</p>
</div>

<p>
List class instances implement the following events:
</p>

<div class="event">
<a name="event-item_added"></a>
<p>
<strong>item_added</strong> - fired when an item is added to the instance.
</p>

<p class="info">
Event listeners will be called with the following parameters:
</p>

<ul>
  <li><strong>items</strong> - An array of items that were added.</li>
</ul>
</div>

<div class="event">
<a name="event-item_removed"></a>
<p>
<strong>item_removed</strong> - fired when an item is removed from the instance.
</p>

<p class="info">
Event listeners will be called with the following parameters:
</p>

<ul>
  <li><strong>items</strong> - An array of items that were removed.</li>
</ul>
</div>

</div>

</body>
</html>
"""
    
    def __init__(self, description, namespace):
        self.description = description
        self.namespace = namespace
        self.docs = []
        
    def generate_docs(self):
        return "\n".join(self.docs)
        
    def generate_code(self):
        from operator import itemgetter
        
        self.docs.append(self.page_doc_start % \
            {
                'namespace': self.namespace,
                'path': self.description['path']
            })
        
        resources = sorted(self.description['resources'], 
                           key=itemgetter('name'))
        classes = [self.generate_class(resource) for resource in resources]
            
        self.docs.append(self.page_doc_end)
            
        return self.page_template % \
            {
                'namespace': self.namespace,
                'path': self.description['path'],
                'classes': '\n'.join(classes)
            }
        
    def generate_class(self, resource):
        from operator import itemgetter
        
        self.docs.append(self.class_doc_start % {'name': resource['name']})
        
        try:
            c_path = "'%s'" % resource['path']
        except KeyError:
            c_path = "null"
        
        try:
            c_id = "'%s'" % resource['id']
            self.docs.append('ID Property: %s<br>' % resource['id'])
        except KeyError:
            c_id = "null"
            self.docs.append('No ID Property<br>')
            
        try:
            stub_list = resource['stub']
            self.docs.append('Stub Properties: %s<br>' % ', '.join(stub_list))
        except KeyError:
            self.docs.append('No Stub Properties<br>')
            stub_list = []
            
        try:
            prop_list = resource['properties']
        except KeyError:
            self.docs.append('No Properties<br>')
            prop_list = []
            
        self.docs.append(self.class_doc_properties)
        
        prop_list = sorted(prop_list, key = itemgetter('name'))
        
        properties = []
        assignments = []
        exposures = []
        for prop in prop_list:
            properties.append(self.generate_property(prop, stub_list))
            
        self.docs.append(self.class_doc_end)
            
        return self.class_template % \
            {
                'name': resource['name'],
                'path': c_path,
                'id': c_id,
                'properties': '\n'.join(properties),
                'assignments': '\n'.join(assignments),
                'exposures': '\n'.join(exposures)
            }
        
    def generate_property(self, prop, stub_list):
        p_type = prop['type']
        name = prop['name']
        cap_name = self.cap_value(name)
        
        stub = "false"
        if name in stub_list:
            stub = "true"
        
        self.docs.append(self.property_doc % \
                         (cap_name, name, self.generate_type_doc(p_type)))
        
        if p_type == 'String' or p_type == 'Boolean' or p_type == 'Number':
            return self.property_simple_template % {
                'name': name,
                'cap_name': cap_name,
                'stub': stub
            }
        else:
            return self.property_complex_template % {
                'name': name,
                'cap_name': cap_name,
                'type': self.generate_type(p_type),
                'stub': stub
            }
        
    def generate_type(self, p_type):
        if not isinstance(p_type, dict):
            return self.type_resource_template % str(p_type)
        elif p_type['type'] == 'Stub':
            return self.type_stub_template % self.generate_type(p_type['of'])
        elif p_type['type'] == 'Link': 
            return self.type_link_template % self.generate_type(p_type['of'])
        elif p_type['type'] == 'List':
            return self.type_list_template % self.generate_type(p_type['of'])
        
    def generate_type_doc(self, p_type):
        if not isinstance(p_type, dict):
            return self.type_resource_doc % {'name': str(p_type)}
        elif p_type['type'] == 'Stub':
            return self.type_stub_doc % self.generate_type_doc(p_type['of'])
        elif p_type['type'] == 'Link': 
            return self.type_link_doc % self.generate_type_doc(p_type['of'])
        elif p_type['type'] == 'List':
            return self.type_list_doc % self.generate_type_doc(p_type['of'])
    
    def cap_value(self, val):
        exp = re.compile('(\_[a-z])', re.IGNORECASE)
        def to_upper(match):
            return match.group()[1].upper()
        cap_val = exp.sub(to_upper, val)
        #return cap_val[0].upper() + cap_val[1:]
        return cap_val


def usage():
    print """\
Usage: 
  jrdl2js [-h|--help] [-u|--url url] [-f|--file file] 
    [-n|--namespace namespace] [-t|--target file] [-d|--doc file]

  Generate JavaScript code from a JRDL document.

Options: 
  -h, --help        Displays this message.
  -u, --url         Load the JRDL document from this url.
  -f, --file        Load the JRDL document from this file.
  -n, --namespace   Namespace of the generated JS classes.
  -t, --target      Name of the target JavaScript file.
  -d, --doc         Name of the documentation file.
"""

def main():
    try:
        opts, args = getopt.getopt(
            sys.argv[1:], 
            "hu:d:n:t:", 
            ["help", "url=", "description=", "namespace=", "target="])
    except getopt.GetoptError:
        usage()
        sys.exit(2)
    
    url = None
    desc_file = None
    desc_str = None
    namespace = None
    target_file = None
    doc_file = None
    
    for opt, att in opts:
        if opt in ('-h', '--help'):
            usage()
            sys.exit()
        if opt in ('-u', '--url'):
            url = att
        elif opt in ('-f', '--file'):
            desc_file = att
        elif opt in ('-n', '--namespace'):
            namespace = att
        elif opt in ('-t', '--target'):
            target_file = att
        elif opt in ('-d', '--doc'):
            doc_file = att
        
    if url:
        url_p = urllib.urlopen(url)
        desc_str = url_p.read()  
        url_p.close()
    elif desc_file:
        desc_str = ''
        
    if desc_str and target_file:
        print "jrdl2js starting..."
        description = simplejson.loads(desc_str)
        gen = Generator(description, namespace)
        target_p = open(target_file, 'w')
        target_p.write(gen.generate_code())
        target_p.close()
        if doc_file:
            doc_p = open(doc_file, 'w')
            doc_p.write(gen.generate_docs())
            doc_p.close()
        print "Done"
    else:
        usage()
        sys.exit()


if __name__ == "__main__":
    main()
    